//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2005-2007
// (c) Denis Perchine (dyp@perchine.com), 2005
//
// Merge polylines.
//

# include "StdAfx.h"
# include "MergeObjects.h"
# include "Map.h"
# include "UndoRedo.h"
# include "Distance.h"
# include "PtTrim.h"

enum case_t {ac, ad, bc, bd} nCase;

static
case_t _FindClosestEnds (const epoints_t & _epoints0, const epoints_t & _epoints1, float & _fDistance) {
	const float fAC = Distance (_epoints0 [0]    .point, _epoints1 [0]    .point);
	const float fAD = Distance (_epoints0 [0]    .point, _epoints1.back ().point);
	const float fBC = Distance (_epoints0.back ().point, _epoints1 [0]    .point);
	const float fBD = Distance (_epoints0.back ().point, _epoints1.back ().point);

	case_t Case = bc;
	_fDistance = fBC;
	if (fAD < _fDistance) {_fDistance = fAD; Case = ad;}
	if (fAC < _fDistance) {_fDistance = fAC; Case = ac;}
	if (fBD < _fDistance) {_fDistance = fBD; Case = bd;}

	return Case;
}

///////////////////////////////////////////////////////////////////

class CMergePolylinesAction : public CEditAction {
  public:
	CMergePolylinesAction (const std::vector<rgn_t *> & _rgns) :
		m_rgns (_rgns),
		m_pRgnNew (NULL)
	{}

	virtual void GetName (string_t & _str) const {_str.append ("merge objects");}
	virtual cu_t CanUndo () const {return cuYes;}
	virtual bool Do ();
	virtual void Undo ();
	virtual void Redo ();

  private:
	const std::vector<rgn_t *> m_rgns;
	rgn_t * m_pRgnNew;

	struct replacement_t {
		rgrestriction_t restrictionOld;
		rgrestriction_t restrictionNew;
	};
	typedef std::map<rgrestriction_t *, replacement_t> replacements_t;
	replacements_t m_replacements;
	void _ReplaceRoadInTurnRestrictions (const rgn_t * _pRoadOld, rgn_t * _pRoadNew);
};

bool CMergePolylinesAction::Do () {
	CCritSec::CLock lock (g_cs);

	//
	// Determine some of properties of the merged object.
	//

	const size_t cRgns = m_rgns.size ();
	assert (cRgns > 1);
	rgn_t & rgn0 = * m_rgns [0];
	const rgn_element_t * const el0 = rgn0.GetNonDeletedElement (0);

	// Type.
	const USHORT ushType = rgn0.ushType;
	const type_desc_t * const pTD = rgn0.GetTypeDesc ();

	// Label.
	string_t strLabel;
	size_t cRgn;
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		const rgn_t & rgn = * m_rgns [cRgn];

		if (! rgn.strLabel.empty ()) {
			strLabel = rgn.strLabel;
			break;
		}
	}

	// Comment.
	string_t strComment;
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		const rgn_t & rgn = * m_rgns [cRgn];

		if (! rgn.strComment.empty () && ! (rgn.strComment == strComment)) {
			if (! strComment.empty ())
				strComment.append ("\r\n");
			strComment.append (rgn.strComment);
		}
	}

	// Level.
	const size_t cLevel = el0->cLevel;

	// EndLevel.
	size_t cEndLevel = 0;
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		const rgn_t & rgn = * m_rgns [cRgn];

		if (rgn.uchEndLevel > cEndLevel)
			cEndLevel = rgn.uchEndLevel;
	}

	// Extra.
	rgn_extra_ptr pExtra;
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		const rgn_t & rgn = * m_rgns [cRgn];

		if (rgn.pExtra) {
			pExtra = rgn.pExtra;
			break;
		}
	}

	// Dir indicator.
	bool bDirIndicator = cRgns > 0;
	// Don't find.
	bool bDontFind     = cRgns > 0;
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		const rgn_t & rgn = * m_rgns [cRgn];
		bDirIndicator = bDirIndicator && (rgn.dwFlags & rgn_t::fDirIndicator) != 0;
		bDontFind     = bDontFind     && (rgn.dwFlags & rgn_t::fDontFind)     != 0;
	}

	// Points.
	polyepoints_t polyepoints;
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		rgn_t & rgn = * m_rgns [cRgn];

		const rgn_element_t * const el = rgn.GetNonDeletedElement (0);
		if (el == NULL || el->points.empty ())
			continue;

		epoints_t epoints;
		Points2EPoints (el->points, el->pExtra ? & el->pExtra->PointExtras : NULL, epoints);

		polyepoints.push_back (epoints_t ());
		polyepoints.back ().swap (epoints);
	}

	size_t cRestPolypoints = cRgns;
	assert (polyepoints.size () == cRestPolypoints);
	while (cRestPolypoints >= 2) {
		//
		// Find the closest pair.
		//
		float fMinDistance = -1;
		polyepoints_t::iterator iClosestRgn0;
		polyepoints_t::iterator iClosestRgn1;
		case_t MinCase;

		for (polyepoints_t::iterator i0 = polyepoints.begin (); i0 != polyepoints.end (); ++ i0) {
			epoints_t & epoints0 = * i0;
			for (polyepoints_t::iterator i1 = polyepoints.begin (); i1 != i0; ++ i1) {
				epoints_t & epoints1 = * i1;

				// Find pair of closest ends.
				float fPairDistance = 0;
				const case_t Case = _FindClosestEnds (epoints0, epoints1, fPairDistance);

				if (fMinDistance < 0 || fMinDistance > fPairDistance) {
					fMinDistance = fPairDistance;
					iClosestRgn0 = i0;
					iClosestRgn1 = i1;
					MinCase = Case;
				}
			}
		}
		assert (fMinDistance >= 0);
		if (fMinDistance < 0)
			break;

		//
		// Merge the pair.
		//

		if (MinCase == ac || MinCase == bd) {
			epoints_t & epoints = * iClosestRgn0;

			// Reverse points.
			std::reverse (epoints.begin (), epoints.end ());

			// Reverse house numbers too.
			numbers_t * pNumbersPrev = NULL;
			const size_t cPoints = epoints.size ();
			for (size_t c = 0; c < cPoints; ++ c) {
				numbers_t & Numbers = epoints [c].extra.Numbers;
				if (! Numbers.bHasNumbers)
					continue;
				Numbers.ReverseSides (pNumbersPrev, c == cPoints - 1);
				pNumbersPrev = & Numbers;
			}
		}

		epoints_t epointsMerged;
		if (MinCase == bc || MinCase == ac) {
			epointsMerged.swap (* iClosestRgn0);
			epointsMerged.insert (epointsMerged.end (), iClosestRgn1->begin (), iClosestRgn1->end ());
		} else {
			assert (MinCase == ad || MinCase == bd);
			epointsMerged.swap (* iClosestRgn1);
			epointsMerged.insert (epointsMerged.end (), iClosestRgn0->begin (), iClosestRgn0->end ());
		}

		iClosestRgn0->swap (epointsMerged);
		polyepoints.erase (iClosestRgn1);

		-- cRestPolypoints;
		assert (polyepoints.size () == cRestPolypoints);
	}

	assert (polyepoints.size () == 1);
	if (polyepoints.empty ())
		return false;
	epoints_t & epoints0 = polyepoints.front ();

	RemoveRepeatingPoints (epoints0);

	points_t points;
	point_extras_t PointExtras;
	EPoints2Points (epoints0, true, points, PointExtras);

// TODO:
//  multi-element objects

	//
	// Create the object.
	//

	m_pRgnNew = & g_map.CreateRgn (rkPolyline);

	// Initialize the object.
	m_pRgnNew->dwFlags |= rgn_t::fModified;
	if (bDirIndicator)
		m_pRgnNew->dwFlags |= rgn_t::fDirIndicator;
	if (bDontFind)
		m_pRgnNew->dwFlags |= rgn_t::fDontFind;

	m_pRgnNew->strLabel  .swap (strLabel);
	m_pRgnNew->strComment.swap (strComment);
	m_pRgnNew->uchEndLevel = cEndLevel;

	m_pRgnNew->SetType (pTD, ushType, rkPolyline);

	m_pRgnNew->pExtra.swap (pExtra); 

	// Create the element of the new object.
	m_pRgnNew->elements.push_back (rgn_element_t ());
	rgn_element_t & elementNew = m_pRgnNew->elements.back ();
	elementNew.cFileOffset = -1;
	elementNew.cLevel      = cLevel;
	elementNew.points.swap (points);
	if (IsNotEmpty (PointExtras)) {
		elementNew.pExtra->PointExtras.swap (PointExtras);

		for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
			const rgn_t & rgn = * m_rgns [cRgn];
			_ReplaceRoadInTurnRestrictions (& rgn, m_pRgnNew);
		}
	}

	m_pRgnNew->RefreshAttributes (g_map);

	//
	// Delete the original objects.
	//
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		rgn_t & rgn = * m_rgns [cRgn];
		rgn.Delete (g_map);
	}

	if (m_pRgnNew->HasRGNodes ())
		g_map.bDirtyRGNodes = true;

	return true;
}

void CMergePolylinesAction::Undo () {
	CCritSec::CLock lock (g_cs);

	// Delete the new object.
	assert (m_pRgnNew);
	m_pRgnNew->Delete (g_map);

	bool bHasRGNodes = false;

	// Restore the old objects.
	const size_t cRgns = m_rgns.size ();
	for (size_t cRgn = 0; cRgn < cRgns; ++ cRgn) {
		rgn_t & rgn = * m_rgns [cRgn];

		rgn.Undelete (g_map);

		if (rgn.HasRGNodes ())
			bHasRGNodes = true;
	}

	// Restore turn restrictions.
	for (replacements_t::iterator ri = m_replacements.begin (); ri != m_replacements.end (); ++ ri)
		* ri->first = ri->second.restrictionOld;

	if (bHasRGNodes)
		g_map.bDirtyRGNodes = true;
}

void CMergePolylinesAction::Redo () {
	CCritSec::CLock lock (g_cs);

	// Restore the new object.
	assert (m_pRgnNew);
	m_pRgnNew->Undelete (g_map);

	bool bHasRGNodes = false;

	// Delete the old objects.
	const size_t cRgns = m_rgns.size ();
	for (size_t cRgn = 0; cRgn < cRgns; ++ cRgn) {
		rgn_t & rgn = * m_rgns [cRgn];

		rgn.Delete (g_map);

		if (rgn.HasRGNodes ())
			bHasRGNodes = true;
	}

	// Replace turn restrictions.
	for (replacements_t::iterator ri = m_replacements.begin (); ri != m_replacements.end (); ++ ri)
		* ri->first = ri->second.restrictionNew;

	if (bHasRGNodes)
		g_map.bDirtyRGNodes = true;
}

void CMergePolylinesAction::_ReplaceRoadInTurnRestrictions (const rgn_t * _pRoadOld, rgn_t * _pRoadNew) {
	assert (m_pRgnNew && ! m_pRgnNew->elements.empty ());
	point_extras_t & PointExtras = m_pRgnNew->elements [0].pExtra->PointExtras;

	const size_t cPointExtras = PointExtras.size ();
	for (size_t c = 0; c < cPointExtras; ++ c) {
		rgnode_t * const pNode = PointExtras [c].GetRGNode ();
		if (! pNode)
			continue;

		typedef std::pair<rgrestrictions_t::iterator, rgrestrictions_t::iterator> rr_t;
		const rr_t rr = g_map.rgrestrictions.equal_range (pNode);
		for (rgrestrictions_t::iterator ir = rr.first; ir != rr.second; ++ ir) {
			rgrestriction_t & r = ir->second;
			if (r.pFromRoad != _pRoadOld && r.pToRoad != _pRoadOld)
				continue;

			// Save turn restriction replacement.
			replacements_t::iterator repi = m_replacements.find (& r);
			if (repi == m_replacements.end ()) {
				repi = m_replacements.insert (replacements_t::value_type (& r, replacement_t ())).first;
				repi->second.restrictionOld = r;
			}

			if (r.pFromRoad == _pRoadOld)
				r.pFromRoad = _pRoadNew;
			if (r.pToRoad   == _pRoadOld)
				r.pToRoad   = _pRoadNew;

			repi->second.restrictionNew = r;
		}
	}
}

////////////////////////////////////////

bool IsMergePolylinesAppliable () {
	size_t cRgns = 0;

	const size_t cSelItems = g_Selection.size ();
	for (size_t cSelItem = 0; cSelItem < cSelItems; ++ cSelItem) {
		find_info_t & SelItem = g_Selection [cSelItem];
		if (! SelItem.pLayer)
			continue;

		const rgn_t & rgn = * SelItem.iRgn;			
		if (rgn.IsDeleted () ||	rgn.IsWTR() || SelItem.cElementOrOrigin != -1)
			continue;

		if (rgn.Kind != rkPolyline)
			continue;

		size_t cRealElements = 0;
 		const size_t cElements = rgn.elements.size ();
		for (size_t cElement = 0; cElement < cElements; ++ cElement) {
			const rgn_element_t & element = rgn.elements [cElement];
			if (element.bDeleted)
				continue;
			++ cRealElements;
		}

		if (cRealElements != 1)
			return false;

		// TODO: check levels.

		++ cRgns;
	}

	return cRgns >= 2;
}

void MergeSelectedPolylines () {
	//
	// Prepare the list of rgns to merge.
	//
	std::vector<rgn_t *> rgns;

	const size_t cSelItems = g_Selection.size ();
	for (size_t cSelItem = 0; cSelItem < cSelItems; ++ cSelItem) {
		find_info_t & SelItem = g_Selection [cSelItem];
		if (! SelItem.pLayer)
			continue;

		rgn_t & rgn = * SelItem.iRgn;			
		if (rgn.IsDeleted () || rgn.IsWTR() || SelItem.cElementOrOrigin != -1)
			continue;

		if (rgn.Kind != rkPolyline)
			continue;

		rgns.push_back (& rgn);
	}

	//
	// Merge them.
	//
	if (rgns.size () >= 2)
		PerformEditAction (new CMergePolylinesAction (rgns));
}
